/*
 *  Copyright (C) 2011 glauco
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.swing.components;

import java.awt.Dimension;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.text.DateFormatter;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.MaskFormatter;
import javax.swing.text.NumberFormatter;
import org.balisunrise.application.Application;
import org.balisunrise.common.FieldAction;
import org.balisunrise.common.IconManager;
import org.balisunrise.common.VisualAction;
import org.balisunrise.swing.commons.GridModel;
import org.balisunrise.swing.components.documents.*;

/**
 *
 * @author glauco
 */
public class ComponentFactory {

    public static final String DATE = "DATE";
    public static final String TIME = "TIME";
    public static final String TIMESTAMP = "TIMESTAMP";

    /**
     * Cria um componente de texto de uma linha de tamanho limitado ou não, e
     * com uppercase ou não.
     *
     * @param length quantidade maxima de caracteres.
     * @param upper uppercase.
     * @return um novo componente de texto.
     */
    public static JTextField textField(int length, boolean upper){

        int columns;
        if(length > 0)
            columns = length;
        else
            columns = 20;

        Document doc = new Document(length);

        if(upper)
            doc.addDocumentFilter(new UpperFilter());

        JTextField field = new JTextField(doc, new String(), columns);
        field.addFocusListener(ComponentSelectAllText.getDefault());
        
        return field;
    }

    /**
     * Cria um componente de text de multiplas linhas e tamanho limitado ou não,
     * número de linhas ou não, com uppercase ou não.
     *
     * @param length quantidade maxima de caracteres.
     * @param rows números de linhas do componente, use zero para calcular.
     * @param upper uppercase.
     * @return um novo componente de text.
     */
    public static JTextArea multLineField(int length, int rows, boolean upper){

        int columns;
        int rows2;
        if (rows > 0){
            if (length > 0)
                columns = (int)Math.ceil(length / rows);
            else
                columns = 50;
            rows2 = rows;
        }
        else{
            if(length <= 200)
                columns = 40;
            else if(length <= 240)
                columns = 50;
            else
                columns = 60;
            rows2 = (int)Math.ceil(length / columns);
        }

        Document doc = new Document(length);

        if(upper)
            doc.addDocumentFilter(new UpperFilter());

        JTextArea textArea = new JTextArea(doc, new String(), rows2, columns);
        FocusTraversalKeys.setComponentFocusTraversalKeys(textArea);
        textArea.setBorder(BorderFactory.createEtchedBorder());

        return textArea;
    }

    /**
     * The following table shows the characters that you can use in the
     * formatting mask:
     *
     * Char - Description
     *  #   - Any valid number (Character.isDigit).
     *  '   - Escape character, used to escape any of the special formatting characters.
     *  U   - Any character (Character.isLetter). All lowercase letters are mapped to uppercase.
     *  L   - Any character (Character.isLetter). All uppercase letters are mapped to lowercase.
     *  A   - Any character or number (Character.isLetter or Character.isDigit).
     *  ?   - Any character (Character.isLetter).
     *  *   - Anything.
     *  H   - Any hex character (0-9, a-f or A-F).
     *
     * @param mask the formatting mask
     * @param inputVerifier the InputVerifier for the component, or null.
     * @return A new JFormattedTextField.
     */
    public static JFormattedTextField maskTextField(String mask,
            InputVerifier inputVerifier){

        JFormattedTextField field;

        try{
            // cria formatter para a mask
            DefaultFormatterFactory formatFactory =
                    new DefaultFormatterFactory(new MaskFormatter(mask));
            // cria componente com o formatFactory
            field = new JFormattedTextField(formatFactory);
        }catch(ParseException e){
            field = new JFormattedTextField();
        }
        // atribui FocusLostBehavior
        field.setFocusLostBehavior(JFormattedTextField.COMMIT);
        // atribui tamanho do campo
        field.setColumns(mask.length());
        // selectiona os campos quando o componente adquire o focus
        field.addFocusListener(ComponentSelectAllText.getDefault());

        if (inputVerifier != null)
            field.setInputVerifier(inputVerifier);

        return field;
    }

    /**
     * Cria um novo componente para dados númericos.
     *
     * @param length quantidade de digitos do campo (#).
     * @param precision número de casas para números decimais.
     * @param negatives permite números negativos.
     * @param groupingSeparator utiliza separador de milhar.
     * @return um novo componente de texto para numeros inteiros ou decimais.
     */
    public static JFormattedTextField numericField(int length, int precision,
            boolean negatives, boolean groupingSeparator){

        // precision deve ser maior ou igual a zero
        // caso for negativo, desconsiderar
        if(precision < 0)
            precision = 0;

        // quantidade de caracteres aceitaveis para o campo
        int realLength = length;
        // se for decimal, mais um caracter para o separador ','
        if(precision > 0)
            realLength++;
        // se tiver separador de milhar
        if(groupingSeparator )
            realLength += (int)Math.floor((length - precision - 1) / 3);
        // se for negativo, mais um carater para o sinal '-'
        if (negatives)
            realLength++;

        // cria documento
        Document doc = new Document(realLength);
        // adiciona um filtro para numericos ao documento
        doc.addDocumentFilter(new NumericFilter(negatives,(precision > 0)));

        // cria um formatter para o campo numerico
        AbstractFormatterFactory formatFactory;
        String mask;
        if(precision > 0)
            mask = (groupingSeparator ? "#,##0." : "#0.") +
                    String
                    .format("%1$#" + precision + "s", "0")
                    .replace(" ", "0");
        else
            mask = (groupingSeparator ? "#,##0" : "#0");
        // cria format
        formatFactory = new DefaultFormatterFactory(
                new NumberFormatter(
                new DecimalFormat(mask)));

        // cria componente com o formatFactory
        JFormattedTextField field = new JFormattedTextField(formatFactory);
        // atribui FocusLostBehavior
        field.setFocusLostBehavior(JFormattedTextField.COMMIT);
        // atribui alinhamento a direita (campos numericos)
        field.setHorizontalAlignment(javax.swing.JTextField.RIGHT);
        // atribui tamanho do campo
        field.setColumns(length);
        // selectiona os campos quando o componente adquire o focus
        field.addFocusListener(ComponentSelectAllText.getDefault());
        // atribui o documento
        field.setDocument(doc);

        return field;
    }

    /**
     * Cria um novo componente de Date, Time, DateTime.
     * @param temporalType formato da data: DATE, TIME, TIMESTAMP.
     * @return novo componente de texto para datas e hora.
     * @see #DATE
     * @see #TIME
     * @see #TIMESTAMP
     */
    public static JFormattedTextField dateField(String temporalType){

        JFormattedTextField field = new JFormattedTextField();

        // tamanho do campo
        int length;
        // formatter para o campo data
        AbstractFormatterFactory formatFactory;
        // filtro para o documento
        DocumentFilter filter;

        int kind = 0;
        if(temporalType.equals(DATE))
            kind = 1;
        else if (temporalType.equals(TIME))
            kind = 2;
        else if (temporalType.equals(TIMESTAMP))
            kind = 3;

        switch (kind) {
            case 1:
                formatFactory = new DefaultFormatterFactory(
                    new DateFormatter(
                    DateFormat.getDateInstance(DateFormat.MEDIUM)));
                length = 10;
                filter = new DateFilter();
                break;
            case 2:
                formatFactory = new DefaultFormatterFactory(
                        new DateFormatter(
                        DateFormat.getTimeInstance(DateFormat.MEDIUM)));
                length = 8;
                filter = new TimeFilter();
                break;
            case 3:
                formatFactory = new DefaultFormatterFactory(
                        new DateFormatter(
                        DateFormat.getDateTimeInstance(
                        DateFormat.MEDIUM,DateFormat.MEDIUM)));
                length = 19;
                filter = new DateTimeFilter();
                break;
            default:
                formatFactory = new DefaultFormatterFactory(
                        new DateFormatter(
                        DateFormat.getDateInstance()));
                length = 20;
                filter = new DateTimeFilter();
        }

        // documento
        Document doc = new Document(length);
        doc.addDocumentFilter(filter);

        // atribui dados
        field.setColumns(length);
        field.setFormatterFactory(formatFactory);
        field.setFocusLostBehavior(JFormattedTextField.COMMIT);
        field.addFocusListener(ComponentSelectAllText.getDefault());
        field.setInputVerifier(new DataInputVerifier());
        field.setDocument(doc);


        return field;
    }

    /**
     * Cria um novo componente JLabel com uma imagem e um evento de MouseClick
     * disparando a ação do FieldAction.
     * @param icon Nome do icone.
     * @param fa FieldAction
     * @return JLabel com a imagem.
     */
    public static JLabel fieldActionIcon(String icon, FieldAction fa){
        IconManager<ImageIcon> im = Application.get().getIconManager();
        ImageIcon ii = im.getIcon(icon);
        JLabel label = new JLabel(ii);
        label.setFocusable(false);
        if (fa != null)
            label.addMouseListener(new FieldActionMouseListener(fa));
        return label;
    }

    public static JButton visualActionButton(String icon, String text,
            VisualAction va){
        IconManager<ImageIcon> im = Application.get().getIconManager();
        ImageIcon ii = im.getIcon(icon);
        Action a = new ButtonAction(text, ii, va);
        JButton button = new JButton(a);
        return button;
    }

    public static JTable gridTable(GridModel model, int height){

        JTable table = new JTable(model);

        // Calcula e Atribui Dimenções
        int totalWidth = 0;
        for(int i = 0; i < model.getColumnCount(); i++){
            int width = model.getColumns()[i].getWidth();
            if (width == 0)
                width = 100;
            table.getColumnModel().getColumn(i).setPreferredWidth(width);
            table.getColumnModel().getColumn(i).setMinWidth(width);
            totalWidth += width;
        }
        // Tamanho do table
        Dimension dimension = new Dimension(totalWidth , height);
        // Dimensiona o table
        table.setMinimumSize(dimension);
        //table.setPreferredSize(dimension);
        table.setPreferredScrollableViewportSize(dimension);

        // outras propriedades
        //table.setSurrendersFocusOnKeystroke(true);
        table.setUpdateSelectionOnSort(false);

        return table;
    }
}
